﻿using System;
using System.ComponentModel;
using System.Data;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using AccessControl.Utility.EnumHelper;
using AccessControl.Utility.Grid;
using DevExpress.XtraEditors.Controls;
using DevExpress.XtraEditors.Repository;
using DevExpress.XtraGrid;
using DevExpress.XtraGrid.Columns;
using DevExpress.XtraGrid.Views.Grid;

namespace AccessControl.Utility.UI
{
    [DefaultEvent("ApplyFilter")]
    public partial class MultiselectCombo : UserControl
    {
        #region variables

        //SortedList<Object, int> SelectedItems;
        private readonly CheckMarkSelection CheckedView;
        private bool AutoUpdating;
        public delegate DataTable fnFillFunc();

        private fnFillFunc FillFunc;
        private SearchFilterCollection filters;
        private bool isBound;
        private string oldFilter;

        public SearchFilterCollection FiltersCollection
        {
            get { return filters; }
        }

        #endregion

        #region Events

        public event EventHandler ApplyFilter;

        #endregion

        #region Members

        private bool _bitwise;
        private bool noCode;
        private bool stringCode;

        public bool IsBound
        {
            get { return isBound; }
        }

        public int MaxBitValue
        {
            get { return filters.MaxBitValue; }
            set { filters.MaxBitValue = value; }
        }

        public RepositoryItemPopupContainerEdit Properties
        {
            get { return txtEdit.Properties; }
        }

        public GridView GridView
        {
            get
            {
                if (!isBound)
                    grdList.DataSource = FillFunc();
                return MainView;
            }
        }

        public string Text
        {
            get { return txtEdit.Text; }
        }

        public bool StringCode
        {
            get { return stringCode; }
            set
            {
                if (value)
                {
                    txtEdit.Properties.Mask.EditMask =
                        "(([^;,:\\-]+[\\-:])?[^;,:\\-]+(;|,))*(([^;,:\\-]+[\\-:])?[^;,:\\-]+)?";
                }
                else
                {
                    txtEdit.Properties.Mask.EditMask = "((\\d+[\\-:])?\\d+(;|,))*((\\d+[\\-:])?\\d+)?";
                }
                stringCode = value;
            }
        }

        public bool NoCode
        {
            get { return noCode; }
            set
            {
                if (MainView.DataRowCount > 0)
                    throw new ApplicationException("NoCode must be set before setting datasource!");
                noCode = value;
                txtEdit.Properties.TextEditStyle = value ? TextEditStyles.DisableTextEditor : TextEditStyles.Standard;
                filters.NoCode = value;
            }
        }

        public string SearchMember { get; set; }
        public string IDMember { get; set; }
        public string CodeMember { get; set; }
        public string TitleMember { get; set; }
        public string GroupMember { get; set; }
        public string GroupMember2 { get; set; }
        public GridView AsyncGridView { get; set; }
        public GridColumn FilterColumn { get; set; }

        public bool Bitwise
        {
            get
            {
                if (filters == null) return _bitwise;
                else return filters.Bitwise;
            }
            set
            {
                if (filters == null) _bitwise = value;
                else filters.Bitwise = _bitwise = value;
            }
        }


        public int FiltersCount
        {
            get { return filters.Count; }
        }

        public void AddFilter(FilterPart filter)
        {
            throw new NotImplementedException();
        }

        public string getFilterString()
        {
            if (filters == null)
                return "";

            return filters.getFilterString(SearchMember);
        }

        public string getFilterStringArray()
        {
           return filters.getFilterString();
        }

        #endregion

        private bool isTextChanged;

        public MultiselectCombo()
        {
            InitializeComponent();
            Height = txtEdit.Height;

            CheckedView = new CheckMarkSelection(MainView);
            CheckedView.NoBatchAction = true;
            CheckedView.SelectionChanged += GridSelectionChanged;
            CheckedView.CheckMarkColumn.VisibleIndex = 100;

            //SelectedItems = new SortedList<Object, int>();
            filters = new SearchFilterCollection();
            filters.NoCode = noCode;
            filters.Bitwise = _bitwise;
        }

        protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
        {
            // Set a fixed height for the control.
            base.SetBoundsCore(x, y, width, txtEdit.Height, specified);
        }

        private void BindData(string idMember, string codeMember, string titleMember, string groupMember,
                              string groupMember2)
        {
            IDMember = idMember;
            CodeMember = codeMember;
            TitleMember = titleMember;
            GroupMember = groupMember;
            GroupMember2 = groupMember2;

            colCode.FieldName = codeMember ?? idMember;
            colIdentity.FieldName = idMember;
            if (TitleMember != null) colName.FieldName = titleMember;
            if (GroupMember != null) colGroup.FieldName = groupMember;
            else colGroup.GroupIndex = -1;
            if (GroupMember2 != null) colGroup2.FieldName = groupMember2;
            else colGroup2.GroupIndex = -1;
            colName.Visible = TitleMember != null;
            colCode.Visible = codeMember != null;
        }

        public void BindData<EnumType>(string idMember, string codeMember, string titleMember, string groupMember,
                                       string groupMember2)
        {
            NoCode = true;
            colCode.Visible = false;
            BindData(idMember, codeMember, titleMember, groupMember, groupMember2);
            grdList.DataSource = typeof(EnumType).ToList<int>();

            isBound = true;
        }

        private void SetAsyncView(GridView asyncGridView, GridColumn filterColumn)
        {
            AsyncGridView = asyncGridView;
            FilterColumn = filterColumn;
            asyncGridView.ColumnFilterChanged += asyncGridView_ColumnFilterChanged;
        }

        private void asyncGridView_ColumnFilterChanged(object sender, EventArgs e) { }

        public void BindData<EnumType>(GridView asyncGridView, GridColumn filterColumn, string idMember,
                                       string codeMember, string titleMember, string groupMember, string groupMember2)
        {
            SetAsyncView(asyncGridView, filterColumn);
            BindData<EnumType>(idMember, codeMember, titleMember, groupMember, groupMember2);
        }

        public void BindData(GridView asyncGridView, GridColumn filterColumn, DataTable Table, string idMember,
                             string codeMember, string titleMember, string groupMember, string groupMember2)
        {
            SetAsyncView(asyncGridView, filterColumn);
            BindData(Table, idMember, codeMember, titleMember, groupMember, groupMember2);
        }

        public void BindData(DataTable Table, string idMember, string codeMember, string titleMember, string groupMember,
                             string groupMember2)
        {
            BindData(idMember, codeMember, titleMember, groupMember, groupMember2);
            if (Table != null)
            {
                grdList.DataSource = Table;
                isBound = true;
            }
        }

        public void ChangeDatasoiurce(fnFillFunc fillFunc)
        {
            FillFunc = fillFunc;
            grdList.DataSource = fillFunc();
        }

        public void BindData(fnFillFunc fillFunc, GridView asyncGridView, GridColumn filterColumn,
                             string idMember, string codeMember, string titleMember, string groupMember,
                             string groupMember2)
        {
            SetAsyncView(asyncGridView, filterColumn);
            BindData(fillFunc, idMember, codeMember, titleMember, groupMember, groupMember2);
        }

        public void BindData(fnFillFunc fillFunc, string idMember, string codeMember, string titleMember,
                             string groupMember, string groupMember2)
        {
            BindData(idMember, codeMember, titleMember, groupMember, groupMember2);
            FillFunc = fillFunc;
            if (FillFunc == null)
            {
                txtEdit.Properties.PopupControl = null;
            }
        }

        public void BindCheckedItems(DataTable checkedTable, string CodeMember)
        {
            CheckedView.ClearSelection();
            if (checkedTable != null)
            {
                if (!isBound)
                    grdList.DataSource = FillFunc();

                int lastRow = -1;
                CheckedView.IsBatchAction = true;
                foreach (DataRow row in checkedTable.Rows)
                {
                    lastRow = CheckedView.View.LocateByValue(0, colCode, row[CodeMember]);
                    CheckedView.SelectRow(lastRow, true);
                }
                CheckedView.IsBatchAction = false;
            }

            UpdateFilters();
        }

        private void MultiselectCombo_Load(object sender, EventArgs e)
        {
            txtEdit.RightToLeft = RightToLeft.No;
        }


        private void MainView_DoubleClick(object sender, EventArgs e)
        {
            if (MainView.IsDataRow(MainView.FocusedRowHandle))
            {
                CheckedView.SelectRow(MainView.FocusedRowHandle, !CheckedView.IsRowSelected(MainView.FocusedRowHandle));
            }
        }

        private void GridSelectionChanged(object sender, CheckMarkSelection.SelectionChangedEventArgs e)
        {
            if (!AutoUpdating)
            {
                if (e.RowHandle < -1) ;
                else if (e.RowHandle == -1)
                {
                    filters.Clear();
                    if (e.IsSelected)
                    {
                        filters.Add(
                            new FilterPart(new DBRow(MainView.GetRowCellValue(MainView.GetRowHandle(0), colCode), 0),
                                           new DBRow(
                                               MainView.GetRowCellValue(
                                                   MainView.GetRowHandle(MainView.DataRowCount - 1), colCode),
                                               MainView.DataRowCount - 1)));
                    }
                }
                else
                {
                    object Code;
                    int DBRowIndex = MainView.GetDataSourceRowIndex(e.RowHandle);
                    string Desc = Convert.ToString(MainView.GetRowCellValue(e.RowHandle, colName));
                    if (stringCode)
                        Code = MainView.GetRowCellValue(e.RowHandle, colCode);
                    else
                        Code = (int)MainView.GetRowCellValue(e.RowHandle, colCode);

                    if (e.IsSelected)
                        filters.AddFilter(new FilterPart(new DBRow(Code, DBRowIndex, Desc)),
                                          SearchFilterCollection.AddTypeConstants.AddOr);
                    else
                    {
                        object NextCode = MainView.GetRowCellValue(MainView.GetRowHandle(DBRowIndex + 1), colCode);
                        object PrevCode = MainView.GetRowCellValue(MainView.GetRowHandle(DBRowIndex - 1), colCode);
                        filters.RemoveFilter(new DBRow(Code, DBRowIndex), PrevCode, NextCode);
                    }
                }
                if (!CheckedView.IsBatchAction)
                {
                    UpdateFilters();
                    if (AsyncGridView != null) DoApply();
                }
            }
        }

        private void txtEdit_EditValueChanged(object sender, EventArgs e)
        {
            if (!AutoUpdating)
            {
                isTextChanged = true;
            }
        }

        public void SetFilter(string filterString)
        {
            if (txtEdit.Text != filterString)
            {
                txtEdit.Text = filterString;
                populateTextFiters();
            }
        }

        public void SetFilter(SearchFilterCollection searchFilters)
        {
            filters = searchFilters;
            UpdateFilters();
        }

        private void txtEdit_Validating(object sender, CancelEventArgs e)
        {
            e.Cancel = !populateTextFiters();
        }

        private bool populateTextFiters()
        {
            if (noCode) return true;
            if (!isBound)
                grdList.DataSource = FillFunc();

            bool Result = true;
            Regex rxPattern, rxSingleCode;

            if (stringCode)
            {
                rxPattern = new Regex("(?<Start>[^;,:\\-]+)[:\\-](?<End>[^;,:\\-]+)|(?<Code>[^;,:\\-]+)");
                rxSingleCode = new Regex("\\d+");
            }
            else
            {
                rxPattern = new Regex("(?<Start>\\d+)[:\\-](?<End>\\d+)|(?<Code>\\d+)");
                rxSingleCode = new Regex("\\d+");
            }
            AutoUpdating = true;
            string currentPart = "";
            CheckedView.ClearSelection();
            filters.Clear();
            MatchCollection parts = rxPattern.Matches(txtEdit.Text);
            int handle = -1;
            foreach (Match m in parts)
            {
                if (m.Groups["Code"].Success)
                {
                    handle = LocateNearestMatch(m.Groups["Code"].Value, 0, true);
                    if (handle > -1 || MainView.DataSource == null)
                    {
                        CheckedView.SelectRow(handle, true);
                        int test;
                        if (stringCode || int.TryParse(m.Groups["Code"].Value, out test))
                        {
                            var Row = new DBRow(MainView.GetRowCellValue(handle, colCode), handle);
                            filters.Add(new FilterPart(Row));
                        }
                        else
                            Result = false;
                    }
                    else
                        txtEdit.Text = txtEdit.Text.Replace(m.Value, "");
                }
                else if (m.Groups["Start"].Success)
                {
                    int startHandle = LocateNearestMatch(m.Groups["Start"].Value, 0, true);

                    int endHandle = LocateNearestMatch(m.Groups["End"].Value, startHandle, false);
                    if (handle == -1) handle = startHandle;

                    int startIndex = Math.Max(-1, MainView.GetDataSourceRowIndex(startHandle));
                    int endIndex = Math.Max(-1, MainView.GetDataSourceRowIndex(endHandle));

                    if (startIndex > endIndex)
                    {
                        int tempIndex = endIndex;
                        endIndex = startIndex;
                        startIndex = tempIndex;
                        tempIndex = endHandle;
                        endHandle = startHandle;
                        startHandle = tempIndex;
                    }

                    var StartRow = new DBRow(MainView.GetRowCellValue(startHandle, colCode), startIndex);
                    var EndRow = new DBRow(MainView.GetRowCellValue(endHandle, colCode), endIndex);
                    if (startHandle > -1 && endHandle < 0)
                    {
                        filters.Add(new FilterPart(StartRow, StartRow));
                        CheckedView.SelectRow(MainView.GetRowHandle(startIndex), true);
                    }
                    else if (startHandle < 0 && endHandle > -1)
                    {
                        filters.Add(new FilterPart(EndRow, EndRow));
                        CheckedView.SelectRow(MainView.GetRowHandle(endIndex), true);
                    }
                    else if (startHandle > -1 && endHandle > -1)
                    {
                        filters.Add(new FilterPart(StartRow, EndRow));
                        for (int index = startIndex; index <= endIndex; index++)
                            CheckedView.SelectRow(MainView.GetRowHandle(index), true);
                    }
                    else if (MainView.DataSource == null)
                    {
                        int start, end;
                        if (stringCode ||
                            (int.TryParse(m.Groups["Start"].Value, out start) &&
                             int.TryParse(m.Groups["End"].Value, out end)))
                            filters.Add(new FilterPart(new DBRow(getValue(m.Groups["Start"].Value), -1),
                                                       new DBRow(getValue(m.Groups["End"].Value), -1)));
                        else
                            Result = false;
                    }
                    else
                    {
                        txtEdit.Text = txtEdit.Text.Replace(m.Value, "");
                    }
                }
            }

            filters.removeExtraFilters();
            UpdateFilters();

            MainView.FocusedRowHandle = handle;

            AutoUpdating = false;

            return Result;
        }

        private int LocateNearestMatch(string code, int startIndex, bool ReturnNextMatch)
        {
            int handle = MainView.LocateByDisplayText(0, colCode, code);
            if (handle > -1) return handle;

            try
            {
                int row = startIndex;
                while (MainView.IsValidRowHandle(row))
                {
                    row += 1;

                    string cellText = MainView.GetRowCellDisplayText(row, colCode);
                    if (cellText.CompareTo(code) > 0) return ReturnNextMatch ? row : row - 1;
                }
            }
            catch { }
            return GridControl.InvalidRowHandle;
        }

        private void SelectRows()
        {
            AutoUpdating = true;
            CheckedView.ClearSelection();
            int selIndex = -1;
            foreach (FilterPart filter in filters)
            {
                for (selIndex = filter.startRow.DBRowIndex; selIndex <= filter.endRow.DBRowIndex; selIndex++)
                    CheckedView.SelectRow(MainView.GetRowHandle(selIndex), true);
            }
            MainView.FocusedRowHandle = MainView.GetRowHandle(selIndex);
            AutoUpdating = false;
        }

        private object getValue(object val)
        {
            if (stringCode)
                return val.ToString();
            else
                return Convert.ToInt32(val);
        }

        private void UpdateFilters()
        {
            AutoUpdating = true;
            txtEdit.Text = filters.ToString();
            AutoUpdating = false;
        }

        private void txtEdit_Popup(object sender, EventArgs e){
            
            //pnlPopup.Width = txtEdit.Width;
            //pnlPopup.Height = 200;

            if (!isBound)
                grdList.DataSource = FillFunc();

            if (isTextChanged)
                populateTextFiters();
            else if (filters.IsChanged)
                SelectRows();
        }

        private void txtEdit_Closed(object sender, ClosedEventArgs e)
        {
            UpdateFilters();
            if (oldFilter != txtEdit.Text) DoApply();
        }

        private void MultiselectCombo_Leave(object sender, EventArgs e)
        {
            if (!FindForm().Disposing)
            {
                if (oldFilter != txtEdit.Text)
                {
                    if (isTextChanged) populateTextFiters();
                    if (txtEdit.DoValidate()) DoApply();
                }
            }
        }


        private void txtEdit_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\r')
            {
                if (isTextChanged) populateTextFiters();
                if (txtEdit.DoValidate()) DoApply();
            }
            else if (e.KeyChar == 'ک')
                e.KeyChar = ';';
            else if (e.KeyChar == 'و')
                e.KeyChar = ',';
        }

        private void MultiselectCombo_Enter(object sender, EventArgs e)
        {
            oldFilter = txtEdit.Text;
        }

        private void DoApply()
        {
            if (AsyncGridView != null)
            {
                AsyncGridView.ActiveFilter.Remove(FilterColumn);
                AsyncGridView.ActiveFilter.Add(FilterColumn,
                                               new ColumnFilterInfo(getFilterString(), NoCode ? txtEdit.Text : ""));
            }
            if (ApplyFilter != null) ApplyFilter(this, EventArgs.Empty);
        }
    }
}
